面试题:竟然有90%的程序员不能把这个算法完全写正确。。。
前语:微信改版后,大量读者留言说,找不到我们的公众号,在此建议大家“置顶”本公众号。如文章写得好,望大家阅读后在右下边“在看”处点个赞,以示鼓励!
作者:liubird 来源:http://t.cn/EiwP9qJ
前几天,在论坛上看到有统计说有90%的程序员不能够写对简单的二分法。二分法不是很简单的吗? 这难道不是耸人听闻?
其实,二分法真的不那么简单,尤其是二分法的各个变种。 最最简单的二分法,就是从一个排好序的数组之查找一个key值。 如下面的程序。
/**
* 二分查找,找到该值在数组中的下标,否则为-1
*/
static int binarySerach(int[] array, int key) {
int left = 0;
int right = array.length - 1;
// 这里必须是 <=
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] == key) {
return mid;
}
else if (array[mid] < key) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return -1;
}
这个程序,相信只要是一个合格的程序员应该都会写。 稍微注意一点, 每次移动left和right指针的时候,需要在mid的基础上+1或者-1, 防止出现死循环, 程序也就能够正确的运行。
但如果条件稍微变化一下, 你还会写吗?如,数组之中的数据可能可以重复,要求返回匹配的数据的最小(或最大)的下标;更近一步, 需要找出数组中第一个大于key的元素(也就是最小的大于key的元素的)下标,等等。 这些,虽然只有一点点的变化,实现的时候确实要更加的细心。 下面列出了这些二分检索变种的实现。
1、找出第一个与key相等的元素
// 查找第一个相等的元素
static int findFirstEqual(int[] array, int key) {
int left = 0;
int right = array.length - 1;
// 这里必须是 <=
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] >= key) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
if (left < array.length && array[left] == key) {
return left;
}
return -1;
}
2、找出最后一个与key相等的元素
// 查找最后一个相等的元素
static int findLastEqual(int[] array, int key) {
int left = 0;
int right = array.length - 1;
// 这里必须是 <=
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] <= key) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
if (right >= 0 && array[right] == key) {
return right;
}
return -1;
}
3、查找第一个等于或者大于Key的元素
// 查找第一个等于或者大于key的元素
static int findFirstEqualLarger(int[] array, int key) {
int left = 0;
int right = array.length - 1;
// 这里必须是 <=
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] >= key) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return left;
}
4、查找第一个大于key的元素
// 查找第一个大于key的元素
static int findFirstLarger(int[] array, int key) {
int left = 0;
int right = array.length - 1;
// 这里必须是 <=
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] > key) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return left;
}
5、查找最后一个等于或者小于key的元素
// 查找最后一个等于或者小于key的元素
static int findLastEqualSmaller(int[] array, int key) {
int left = 0;
int right = array.length - 1;
// 这里必须是 <=
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] > key) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return right;
}
6、查找最后一个小于key的元素
// 查找最后一个小于key的元素
static int findLastSmaller(int[] array, int key) {
int left = 0;
int right = array.length - 1;
// 这里必须是 <=
while (left <= right) {
int mid = (left + right) / 2;
if (array[mid] >= key) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return right;
}
接下来,大家可以对这四种变种算法进行相应的测试。
很多的时候,应用二分检索的地方都不是直接的查找和key相等的元素,而是使用上面提到的二分检索的各个变种,熟练掌握了这些变种,当你再次使用二分检索的检索的时候就会感觉的更加的得心应手了。
(完)
这里,我留给大家一个问题,这种 mid = (left + right) / 2 的写法有什么不足,该怎么改进呢?如果你用过jdk的二分查找,肯定就知道答案了(提示:Collections.binarySearch())。这里不提倡大家为了读源码而读源码,一般来说,带有目的性的读源码才会有较大的帮助。
最后,也欢迎各位读者入群来交流切磋,戳这里:咱们来一起抱团取暖,好吗?
---END---
热文推荐
面试题:jdk那些类的底层实现使用过位运算,并且给你印象最深?